/*
 * @Author: xiaosihan
 * @Date: 2022-05-04 23:01:36
 * @Last Modified by: xiaosihan
 * @Last Modified time: 2024-11-07 21:57:39
 */

import {
  Camera,
  Clock,
  EventDispatcher,
  Object3D,
  Triangle,
  Vector3,
} from "three";
import { Capsule } from "three/examples/jsm/math/Capsule";
import { Octree } from "three/examples/jsm/math/Octree";
import { clamp, degToRad } from "three/src/math/MathUtils";

/**
 * 第一人称控制器
 *
 * @export
 * @class FPScontrol
 * @extends {EventDispatcher}
 */
export default class FPScontrol extends EventDispatcher {
  canvas!: HTMLCanvasElement;

  constructor(camera: Camera, canvas: HTMLCanvasElement) {
    super();

    // 相机对象
    this.camera = camera;

    this.camera.rotation.order = "YXZ";

    this.canvas = canvas;

    this.init(canvas);
  }

  // 是否启用
  enable = false;

  GRAVITY = 30; // 重力
  STEPS_PER_FRAME = 5; // 每帧碰撞计算的次数
  speed = 1; // 速度
  camera!: Camera;
  // 移动时是否保持惯性 由外部来配置
  inertia = true;

  // 八叉树数组
  octreeMap: Map<number, Octree> = new Map();
  octree = (() => {
    const octree = new Octree();
    octree.getCapsuleTriangles = (capsule: Capsule, triangles: Triangle[]) => {
      const octrees = Array.from(this.octreeMap.values());
      for (let octree of octrees) {
        for (let i = 0; i < octree.subTrees.length; i++) {
          const subTree = octree.subTrees[i];
          if (!capsule.intersectsBox(subTree.box)) continue;
          if (subTree.triangles.length > 0) {
            for (let j = 0; j < subTree.triangles.length; j++) {
              if (triangles.indexOf(subTree.triangles[j]) === -1)
                triangles.push(subTree.triangles[j]);
            }
          } else {
            subTree.getCapsuleTriangles(capsule, triangles);
          }
        }
      }
      return triangles;
    };
    return octree;
  })();
  addOctree(obj: Object3D) {
    const octree = new Octree();
    octree.fromGraphNode(obj);
    this.octreeMap.set(obj.id, octree);
    return octree;
  }

  // 用户自己的碰撞体积
  playerCollider = new Capsule(
    new Vector3(0, 0.35, 0),
    new Vector3(0, 1, 0),
    0.35
  );

  // 时钟对象
  clock = new Clock();

  prePlayerVelocity = new Vector3();
  playerVelocity = new Vector3();

  // 起跳中
  jumping = false;

  playerDirection = new Vector3();

  prePlayerOnFloor = false;
  playerOnFloor = false;
  mouseTime = 0;
  isMousedown = false;
  keyStates: { [key: string]: boolean } = {};

  init(canvas: HTMLCanvasElement) {
    this.playerCollider.end.set(0, 1, 0);
    document.addEventListener("keydown", this.keydown);
    document.addEventListener("keyup", this.keyup);
    canvas.addEventListener("pointerdown", this.mousedown);
    document.addEventListener("pointerup", this.mouseup);
    document.body.addEventListener("pointermove", this.mousemove);
    this.enable = true;
  }

  keydown = (event: KeyboardEvent) => {
    this.keyStates[event.code] = true;
  };

  keyup = (event: KeyboardEvent) => {
    this.keyStates[event.code] = false;
  };

  mousedown = () => {
    this.isMousedown = true;
    this.mouseTime = performance.now();
  };

  mouseup = () => {
    this.isMousedown = false;
  };

  mousemove = (event: MouseEvent) => {
    if (this.enable && this.isMousedown) {
      this.camera.rotation.y -= event.movementX / 500;
      this.camera.rotation.x -= event.movementY / 500;
    }
  };

  // 获取前进的向量
  getForwardVector = () => {
    this.camera.getWorldDirection(this.playerDirection);
    this.playerDirection.y = 0;
    this.playerDirection.normalize();
    return this.playerDirection;
  };

  // 获取 后退的向量
  getSideVector = () => {
    this.camera.getWorldDirection(this.playerDirection);
    this.playerDirection.y = 0;
    this.playerDirection.normalize();
    this.playerDirection.cross(this.camera.up);
    return this.playerDirection;
  };

  // 玩家移动控制器
  controls = (deltaTime: number) => {
    const speedDelta = deltaTime * (this.playerOnFloor ? 25 : 8) * this.speed;
    // const speedDelta = this.speed;
    if (this.keyStates["KeyW"]) {
      this.playerVelocity.add(
        this.getForwardVector().multiplyScalar(speedDelta)
      );
    }
    if (this.keyStates["KeyS"]) {
      this.playerVelocity.add(
        this.getForwardVector().multiplyScalar(-speedDelta)
      );
    }
    if (this.keyStates["KeyA"]) {
      this.playerVelocity.add(this.getSideVector().multiplyScalar(-speedDelta));
    }
    if (this.keyStates["KeyD"]) {
      this.playerVelocity.add(this.getSideVector().multiplyScalar(speedDelta));
    }

    if (
      this.playerOnFloor &&
      !this.keyStates["KeyW"] &&
      !this.keyStates["KeyS"] &&
      !this.keyStates["KeyA"] &&
      !this.keyStates["KeyD"]
    ) {
      if (!this.inertia) {
        // 关闭惯性时就大幅增加动能的衰减
        this.playerVelocity.x *= 0.85;
        this.playerVelocity.z *= 0.85;
      }
    }

    if (this.playerOnFloor) {
      if (this.keyStates["Space"]) {
        this.playerVelocity.y = 15;
        this.jumping = true;
        this.dispatchEvent({ type: "jump_start" } as never);
      }
    }
  };

  // 更新玩家的位置
  updatePlayer = (deltaTime: number) => {
    let damping = Math.exp(-4 * deltaTime) - 1;
    if (!this.playerOnFloor) {
      this.playerVelocity.y -= this.GRAVITY * deltaTime;
      damping *= 0.1;
    }
    this.playerVelocity.addScaledVector(this.playerVelocity, damping);
    const deltaPosition = this.playerVelocity.clone().multiplyScalar(deltaTime);
    this.playerCollider.translate(deltaPosition);
    this.playerCollisions();
    this.camera.position.copy(this.playerCollider.end);
    this.camera.rotation.x = clamp(
      this.camera.rotation.x,
      degToRad(-89),
      degToRad(89)
    );
  };

  // 玩家的碰撞检测
  playerCollisions() {
    this.playerOnFloor = false;
    // 遍历 八叉树 做碰撞检测
    const result = this.octree.capsuleIntersect(this.playerCollider);
    if (result) {
      this.playerOnFloor = result.normal.y > 0;
      if (!this.playerOnFloor) {
        this.playerVelocity.addScaledVector(
          result.normal,
          -result.normal.dot(this.playerVelocity)
        );
      }
      const { x: preX, y: preY, z: preZ } = this.prePlayerVelocity;
      const { x, y, z } = this.playerVelocity;

      if (Math.abs(preX + preZ) < 0.1 && Math.abs(x + z) > 0.1) {
        this.dispatchEvent({ type: "move_start" } as never);
      } else if (Math.abs(preX + preZ) > 0.1 && Math.abs(x + z) < 0.1) {
        this.dispatchEvent({ type: "move_end" } as never);
      }

      this.prePlayerVelocity.copy(this.playerVelocity);

      this.playerCollider.translate(result.normal.multiplyScalar(result.depth));
    }

    if (this.jumping && !this.prePlayerOnFloor && this.playerOnFloor) {
      // 分发落地事件
      this.jumping = false;
      this.dispatchEvent({ type: "jump_end" } as never);
    }

    this.prePlayerOnFloor = this.playerOnFloor;
  }

  // 更新控制器的位置
  teleportPlayerIfOob = () => {
    if (this.camera.position.y <= -10) {
      this.playerCollider.start.set(0, 0.35, 0);
      this.playerCollider.end.set(0, 1, 10);
      this.playerCollider.radius = 0.35;
      this.camera.position.copy(this.playerCollider.end);
      //   this.camera.position.set(0, 3, 0);
    }
  };

  // 更新
  update() {
    if (this.enable) {
      const deltaTime =
        Math.min(0.05, this.clock.getDelta()) / this.STEPS_PER_FRAME;
      for (let i = 0; i < this.STEPS_PER_FRAME; i++) {
        this.controls(deltaTime);
        this.updatePlayer(deltaTime);
        this.teleportPlayerIfOob();
      }
    }
  }

  // 销毁事件
  disposer() {
    this.enable = false;
    document.removeEventListener("keydown", this.keydown);
    document.removeEventListener("keyup", this.keyup);
    this.canvas.removeEventListener("pointerdown", this.mousedown);
    document.removeEventListener("pointerup", this.mouseup);
    document.body.removeEventListener("pointermove", this.mousemove);
  }
}
